/**
 * 
 */
package com.android.vocalNotes;

import java.io.File;
import java.io.FileNotFoundException;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;

import com.android.vocalNotes.VocalNotes;

/**
 * 
 * This class implements a content provider that manages data and provides
 * simple methods for accessing them to clients.
 * 
 * @author Daniela Cabiddu, Maurizio Kovacic, Giorgio Marcias
 */
public class VocalNotesContentProvider extends ContentProvider {

   /**
    * The name of the database.
    */
   private static final String DATABASE_NAME = "vocalNotes.db";

   /**
    * The version of the database.
    */
   private static final int DATABASE_VERSION = 1;

   /**
    * The name of the _data column.
    */
   private static final String _DATA = "_data";

   /**
    * The name of the _display_name column.
    */
   private static final String _DISPLAY_NAME = "_display_name";

   /**
    * The name of the _size column.
    */
   private static final String _SIZE = "_size";

   /**
    * The standard first part of the name of the audio files.
    */
   private static final String FILENAME = "vocal_note_";

   /**
    * The default extension of the audio files.
    */
   private static final String DEFAULT_EXTENSION = ".amr";

   /**
    * The standard name of the JSON file used for sharing one vocal note.
    */
   private static final String JSON_FILE_NAME = "vocalNote.json";

   /**
    * The command create table string for the vocal notes.
    */
   private static final String NOTES_TABLE_CREATE = "CREATE TABLE " // table of
                                                                    // the vocal
                                                                    // notes
         + VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME
         + " ( "
         + VocalNotes.VocalNotesContentProviderAPI._ID
         + " INTEGER PRIMARY KEY AUTOINCREMENT, "
         + VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_TITLE
         + " TEXT NOT NULL, "
         + VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE
         + " LONG INTEGER NOT NULL, "
         + VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING
         + " INTEGER, "
         + VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DESCRIPTION
         + " TEXT, " + _DATA + " TEXT UNIQUE NOT NULL);";

   /**
    * The command create table string for the JSON file.
    */
   private static final String JSON_TABLE_CREATE = "CREATE TABLE " // table of
                                                                   // the JSON
                                                                   // file
         + VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME
         + " ( "
         + VocalNotes.VocalNotesContentProviderAPI._ID
         + " INTEGER PRIMARY KEY AUTOINCREMENT, "
         + _DATA
         + " TEXT UNIQUE NOT NULL, " + _DISPLAY_NAME + " TEXT, "
         + _SIZE
         + " INTEGER);";

   /**
    * The SQLiteOpenHelper instance that is responsible of creating and updating
    * the database.
    */
   private DBOpenHelper dbOpenHelper;

   /**
    * Constant NOTES is used to distinguish between types of data to be returned
    * in some methods like query(). It identifies a set of vocal notes.
    */
   private static final int NOTES = 1;

   /**
    * Constant NOTES_ID is used to distinguish between types of data to be
    * returned in some methods like query(). It identifies a single vocal note.
    */
   private static final int NOTES_ID = 2;

   /**
    * Constant JSON is used to distinguish between types of data to be returned
    * in some methods like query(). It identifies a set of JSON files.
    */
   private static final int JSON = 3;

   /**
    * Constant JSON_ID is used to distinguish between types of data to be
    * returned in some methods like query(). It identifies a single JSON file.
    */
   private static final int JSON_ID = 4;

   /**
    * A UriMatcher instance is used for matching URIs with constants to identify
    * which type of data the client requests. It is initialized with a NO_MATCH
    * constant that identifies URIs no matching with right types.
    */
   private static final UriMatcher URImatcher = new UriMatcher(
         UriMatcher.NO_MATCH);

   /*
    * This static block instantiates a set of static objects. Statically it adds
    * constants to the UriMatcher instance that map to types of resources.
    */
   static {
      /*
       * Adds the mapping between URIs that end with "notes" and NOTES code.
       */
      URImatcher.addURI(VocalNotes.AUTHORITY, VocalNotes.PATH_NOTES, NOTES);

      /*
       * Adds the mapping between URIs that end with "notes/#", in which "#" is
       * a wildcard that stays for "any integer number", and NOTES_ID code.
       */
      URImatcher.addURI(VocalNotes.AUTHORITY, VocalNotes.PATH_NOTES + "/#",
            NOTES_ID);

      /*
       * Adds the mapping between URIs that end with "json" and JSON code.
       */
      URImatcher.addURI(VocalNotes.AUTHORITY, VocalNotes.PATH_JSON, JSON);

      /*
       * Adds the mapping between URIs that end with "json/#", in which "#" is a
       * wildcard that stays for "any integer number", and JSON_ID code.
       */
      URImatcher.addURI(VocalNotes.AUTHORITY, VocalNotes.PATH_JSON + "/#",
            JSON_ID);
   }

   /*
    * A projection map between field names of the cursors returned by the
    * content provider and the field names of the SQL database is not necessary
    * because they are identical.
    */

   /**
    * The DBOpenHelper class is responsible to create and upgrade the database
    * files that store vocal notes.
    */
   private static class DBOpenHelper extends SQLiteOpenHelper {

      /**
       * The constructor doesn't create the database itself, but stores the
       * command string for this purpose. Its creation happens at onCreate()
       * call.
       * 
       * @param context
       *           The context.
       */
      public DBOpenHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
      }

      /**
       * This method is responsible of creating the database table(s).
       * 
       * @param db
       *           The database.
       */
      @Override
      public void onCreate(SQLiteDatabase db) {
         // creates the table for the vocal notes
         db.execSQL(NOTES_TABLE_CREATE);

         // creates the table for the JSON file
         db.execSQL(JSON_TABLE_CREATE);
      }

      /**
       * This method is responsible of upgrading the database when the database
       * version changes.
       * 
       * @param db
       *           The database.
       * @param oldVersion
       *           The old version number.
       * @param newVersion
       *           The new version number.
       */
      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         // deletes old database data

         // vocal notes table
         db.execSQL("DROP TABLE IF EXISTS "
               + VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME);

         // json table
         db.execSQL("DROP TABLE IF EXISTS "
               + VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME);

         // recreate the database table
         onCreate(db);
      }
   }

   /**
    * This method is responsible of deleting a (set of) resource(s) determined
    * by the content: URI or/and the WHERE clause.
    * 
    * @param uri
    *           The URI that identifies the resource(s).
    * @param where
    *           A string that contains a set of column names for the WHERE
    *           clause.
    * @param whereArgs
    *           A set of values for the WHERE clause column names.
    * @return The number of rows affected by the delete operation.
    */
   @Override
   public int delete(Uri uri, String where, String[] whereArgs) {
      int number_of_rows = 0; // the number of rows the operation will affect
      long rowId; // the id of a single row
      Cursor cursor; // a cursor for queries
      File file; // the File object for audio files

      // get the database
      SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

      // match the URI
      switch (URImatcher.match(uri)) {
      // case of a set of vocal notes
      case NOTES:
         // get the cursor for first deleting all the audio files
         cursor = db.query(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
               new String[] { _DATA }, where, whereArgs, null, null, null);

         // if result isn't empty
         if (cursor.moveToFirst()) {
            // delete the files
            do {
               // get the audio file
               file = new File(cursor.getString(cursor.getColumnIndex(_DATA)));

               // delete it
               file.delete();

            } while (cursor.moveToNext());
         }

         // call to the SQLiteDatabase delete() method
         number_of_rows = db.delete(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME, where,
               whereArgs);
         break;

      // case of a single vocal note
      case NOTES_ID:
         // get the ID
         rowId = ContentUris.parseId(uri);

         // get the cursor and its _data value for this URI
         cursor = db.query(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
               new String[] { _DATA },
               VocalNotes.VocalNotesContentProviderAPI._ID + " = " + rowId
                     + (!TextUtils.isEmpty(where) ? " AND " + where : ""),
               whereArgs, null, null, null);

         // delete the file
         if (cursor.moveToFirst()) {
            // get the audio file
            file = new File(cursor.getString(cursor.getColumnIndex(_DATA)));

            // delete it
            file.delete();
         }

         // call to the SQLiteDatabase delete() method
         number_of_rows = db.delete(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
               VocalNotes.VocalNotesContentProviderAPI._ID + " = " + rowId
                     + (!TextUtils.isEmpty(where) ? " AND " + where : ""),
               whereArgs);
         break;

      // case of a set of JSON files
      case JSON:
         // call to the SQLiteDatabase delete() method
         number_of_rows = db.delete(
               VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME, where,
               whereArgs);
         break;

      // case of a single JSON file
      case JSON_ID:
         // get the ID
         rowId = ContentUris.parseId(uri);
         // call to the SQLiteDatabase delete() method
         number_of_rows = db.delete(
               VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME,
               VocalNotes.VocalNotesContentProviderAPI._ID + " = " + rowId
                     + (!TextUtils.isEmpty(where) ? " AND " + where : ""),
               whereArgs);
         break;

      // default case: illegal URI
      default:
         // throw an exception
         throw new IllegalArgumentException("Unknown URI: " + uri);
      }

      // notify observers for the change
      getContext().getContentResolver().notifyChange(uri, null);

      // return the number of rows affected by the operation
      return number_of_rows;
   }

   /**
    * This method return the MIME data type of the URI given as parameter.
    * 
    * @param uri
    *           The URI whose MIME type is requested.
    * @return A string representing the MIME type of the URI.
    * @throws IllegalArgumentException
    *            if the URI given as parameter is invalid.
    */
   @Override
   public String getType(Uri uri) throws IllegalArgumentException {
      // match using the UriMatcher instance
      switch (URImatcher.match(uri)) {
      case NOTES:
         // this is the case of a set of vocal notes
         return VocalNotes.NOTES_CONTENT_TYPE;

      case NOTES_ID:
         // this is the case of a single vocal note
         return VocalNotes.NOTES_CONTENT_TYPE_ITEM;

      case JSON:
         // this is the case of a set of vocal notes
         return VocalNotes.JSON_CONTENT_TYPE;

      case JSON_ID:
         // this is the case of a single vocal note
         return VocalNotes.JSON_CONTENT_TYPE_ITEM;

         // default case: illegal URI
      default:
         // throw an exception
         throw new IllegalArgumentException("Unknown URI: " + uri);
      }
   }

   /**
    * This private method is responsible of validating the content values to be
    * inserted.
    * 
    * @param values
    *           The content values to be validated.
    */
   private void validateContentValues(ContentValues values) {
      // check if it has the title: it must be contained
      if (!values
            .containsKey(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_TITLE))
         // throw an exception if it isn't
         throw new IllegalArgumentException(
               "Content values doesn't contain the title.");

      // check if it has the date: it must be contained
      if (!values
            .containsKey(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE))
         // insert the current time as it
         values.put(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE,
               Long.valueOf(System.currentTimeMillis()));

      // check if it has the audio file extension
      if (values
            .containsKey(VocalNotes.VocalNotesContentProviderAPI.FILE_EXTENSION)) {
         // if yes then put it into the _DATA pair (to be used later)
         values.put(
               _DATA,
               values.getAsString(VocalNotes.VocalNotesContentProviderAPI.FILE_EXTENSION));

         // delete the FILE_EXTENSION pair
         values.remove(VocalNotes.VocalNotesContentProviderAPI.FILE_EXTENSION);

      } else {
         // else put into the _DATA pair the default extension
         values.put(_DATA, DEFAULT_EXTENSION);
      }
   }

   /**
    * This method is responsible of inserting a new row (vocal note) into the
    * database. Note that the title is required. This can also insert a new JSON
    * row but it is used only once since that there will be a unique row during
    * the total life of the application. Be sure to not invoke this insertion
    * more than once. To insert the audio file use the
    * ContentResolver.openOutputStream() with the uri returned by this method.
    * 
    * @param uri
    *           The content: URI for this content provider: only
    *           {@link VocalNotes.VocalNotesContentProviderAPI.CONTENT_URI} is
    *           allowed.
    * @param values
    *           The content values to be inserted into the new row.
    * @return The URI of the new row.
    */
   @Override
   public Uri insert(Uri uri, ContentValues values) {
      long rowId; // the id of a row

      // get the database
      SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

      // match the uri
      switch (URImatcher.match(uri)) {
      // case of a vocal note
      case NOTES:
         // if content values are null
         if (values == null) {
            // create they empty
            values = new ContentValues();
         }

         // validate content values
         validateContentValues(values);

         // insert the values into a new row of the database
         rowId = db.insert(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME, null,
               values);

         // check if the insert operation is ended without errors
         if (rowId > 0) {
            // creates an empty file in the external storage root directory of
            // this application for this vocal note
            File file = new File(getContext().getExternalFilesDir(null),
                  FILENAME + rowId + values.getAsString(_DATA));

            // update this row by storing the right _DATA URL of the file

            // clear content values
            values.clear();

            // put the _data value into content values
            values.put(_DATA, file.getAbsolutePath());

            // update the row
            db.update(VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
                  values, VocalNotes.VocalNotesContentProviderAPI._ID + " = "
                        + rowId, null);

            // create the URI for the new row
            Uri newURI = ContentUris.withAppendedId(
                  VocalNotes.CONTENT_URI_NOTES, rowId);

            // notify observers for the change
            getContext().getContentResolver().notifyChange(newURI, null);

            // return the new URI
            return newURI;
         }

         // else throw a new exception
         throw new SQLException("Failed to insert new row into " + uri);

         // case of a JSON file (URL)
      case JSON:
         // construct the content values
         values = new ContentValues();

         // creates an empty file in the external storage root directory of this
         // application
         File json_file = new File(getContext().getExternalFilesDir(null),
               JSON_FILE_NAME);

         // put the filepath into content values
         values.put(_DATA, json_file.getAbsolutePath());

         // put the filename into content values
         values.put(_DISPLAY_NAME, JSON_FILE_NAME);

         // insert the values into a new row of the database
         rowId = db.insert(
               VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME, null,
               values);

         // check if the insert operation is ended without errors
         if (rowId > 0) {
            // create the URI for the new row
            Uri newURI = ContentUris.withAppendedId(
                  VocalNotes.CONTENT_URI_JSON, rowId);

            // notify observers for the change
            getContext().getContentResolver().notifyChange(newURI, null);

            // return the new URI
            return newURI;
         }

         // else throw a new exception
         throw new SQLException("Failed to insert new row into " + uri);

         // default case: illegal URI
      default:
         // throw an exception
         throw new IllegalArgumentException("Unknown or wrong URI: " + uri);
      }
   }

   /**
    * This method is responsible of initializing the content provider by
    * creating a new SQLiteOpenHelper instance. The system creates it at the
    * first resolver request by a client.
    * 
    * @return The TRUE boolean value.
    */
   @Override
   public boolean onCreate() {
      /*
       * Creates a new SQLiteOpeneHelper object. The database isn't opened until
       * something tries to access it (like a reading operation), and it is only
       * created if it doesn't already exist.
       */
      dbOpenHelper = new DBOpenHelper(getContext());

      // return
      return true;
   }

   /**
    * This method is responsible of providing a cursor that access to the data
    * requested.
    * 
    * @param uri
    *           The URI that identifies the resource(s).
    * @param projection
    *           A set of column names to be projected into the resulting rows.
    *           It is ignored in the case of a {@link #VocalNotes.CONTENT_URI_JSON} URI.
    * @param selection
    *           A string that contains a set of column names for the WHERE
    *           clause.
    * @param selectionArgs
    *           A set of values for the WHERE clause column names.
    * @param sortOrder
    *           The order key by which to order the resulting rows.
    * @return A Cursor instance that manages the resulting rows.
    */
   @Override
   public Cursor query(Uri uri, String[] projection, String selection,
         String[] selectionArgs, String sortOrder) {

      // check the sort order: if null or empty, set it to the default order
      if (TextUtils.isEmpty(sortOrder)) {
         sortOrder = VocalNotes.VocalNotesContentProviderAPI.DEFAULT_ORDER;
      }

      // get the database
      SQLiteDatabase db = dbOpenHelper.getReadableDatabase();

      // the cursor to be returned
      Cursor cursor;

      // match the URI
      switch (URImatcher.match(uri)) {
      // case of a set of vocal notes
      case NOTES:
         // check the projection
         if (projection == null) {
            projection = VocalNotes.VocalNotesContentProviderAPI.PROJECTION;
         }

         // call the SQLiteDatabase query() method
         cursor = db.query(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
               projection, selection, selectionArgs, null, null, sortOrder);

         break;

      // case of a single vocal note
      case NOTES_ID:
         // check the projection
         if (projection == null) {
            projection = VocalNotes.VocalNotesContentProviderAPI.PROJECTION;
         }

         // call the SQLiteDatabase query() method in which the _id selection is
         // added
         cursor = db.query(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
               projection,
               VocalNotes.VocalNotesContentProviderAPI._ID
                     + " = "
                     + ContentUris.parseId(uri)
                     + (!TextUtils.isEmpty(selection) ? " AND (" + selection
                           + ")" : ""), selectionArgs, null, null, sortOrder);

         break;

      // case of a set of JSON files
      case JSON:
         // call the SQLiteDatabase query() method
         cursor = db.query(
               VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME,
               projection, selection, selectionArgs, null, null, null);

         break;

      // case of a single JSON file
      case JSON_ID:
         // call the SQLiteDatabase query() method in which the _id selection is
         // added
         cursor = db.query(
               VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME,
               projection,
               VocalNotes.VocalNotesContentProviderAPI._ID
                     + " = "
                     + ContentUris.parseId(uri)
                     + (!TextUtils.isEmpty(selection) ? " AND (" + selection
                           + ")" : ""), selectionArgs, null, null, null);

         break;

      // default case: throws an exception
      default:
         // throw an exception
         throw new IllegalArgumentException("Unknown URI: " + uri);
      }

      // set what URI the cursor to watch so that it will be notified when data
      // changes
      cursor.setNotificationUri(getContext().getContentResolver(), uri);

      // return the cursor
      return cursor;
   }

   /**
    * This method is responsible of modifying (updating) a set of values
    * corresponding to relative columns of one or more rows in the table
    * identified by the content: URI. If you want to update the audio file of
    * the vocal note identified by the content: URI, use the
    * ContentResolver.openOutputStream() passing it the same content: URI.
    * 
    * @param uri
    *           The URI that identifies the resource(s).
    * @param values
    *           The content values to be inserted into the existing row(s).
    * @param where
    *           A string that contains a set of column names for the WHERE
    *           clause.
    * @param whereArgs
    *           A set of values for the WHERE clause column names.
    * @return The number of rows affected by the delete operation.
    */
   @Override
   public int update(Uri uri, ContentValues values, String selection,
         String[] selectionArgs) {
      int number_of_rows = 0; // the number of rows the operation will affect
      long rowId; // the id of the row

      // get the database
      SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

      // match the URI
      switch (URImatcher.match(uri)) {
      // case of a set of vocal notes
      case NOTES:
         // call to the SQLiteDatabase update() method
         number_of_rows = db.update(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
               values, selection, selectionArgs);

         break;

      // case of a single vocal note
      case NOTES_ID:
         // get the ID
         rowId = ContentUris.parseId(uri);

         // call to the SQLiteDatabase update() method
         number_of_rows = db.update(
               VocalNotes.VocalNotesContentProviderAPI.NOTES_TABLE_NAME,
               values, VocalNotes.VocalNotesContentProviderAPI._ID
                     + " = "
                     + rowId
                     + (!TextUtils.isEmpty(selection) ? " AND " + selection
                           : ""), selectionArgs);

         break;

      // case of a set of JSON files
      case JSON:
         // call to the SQLiteDatabase update() method
         number_of_rows = db.update(
               VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME, values,
               selection, selectionArgs);

         break;

      // case of a single JSON file
      case JSON_ID:
         // get the ID
         rowId = ContentUris.parseId(uri);

         // call to the SQLiteDatabase update() method
         number_of_rows = db.update(
               VocalNotes.VocalNotesContentProviderAPI.JSON_TABLE_NAME, values,
               VocalNotes.VocalNotesContentProviderAPI._ID
                     + " = "
                     + rowId
                     + (!TextUtils.isEmpty(selection) ? " AND " + selection
                           : ""), selectionArgs);

         break;

      // default case: illegal URI
      default:
         // throw an exception
         throw new IllegalArgumentException("Unknown URI: " + uri);
      }

      // notify observers for the change
      getContext().getContentResolver().notifyChange(uri, null);

      // return the number of rows affected by the operation
      return number_of_rows;
   }

/**
	 * This method is responsible of giving access to the file
	 * whose URL is stored in the "_data" field.
	 * It is useful for writing and/or reading the audio file
	 * of a single vocal note or the JSON file
	 * to send.
	 * Use ContentResolver.openInputStream(Uri uri) to read
	 * and ContentResolver.openOutputStream(Uri uri) to write
	 * into the file.
	 * Get the uri of the JSON file by:
	 * - first calling
	 * Cursor c = ContentResolver.query({@link #VocalNotes.CONTENT_URI_JSON},
	 * new String[] = {VocalNotes.VocalNotesContentProviderAPI._ID}, null,
	 * null, null); this will return a Cursor object that iterates into a set
	 * of a single row, that stores the ID; if the Cursor iterates into a set
	 * of more than one row then there is an error;
	 * c.moveToFirst();
	 * long id = c.getLong(c.getColumnIndex(
	 * 	VocalNotes.VocalNotesContentProviderAPI._ID));
	 * in this way you get the ID;
	 * - then construct the content: URI by calling
	 * Uri uri = ContentUris.withAppendedId({@link #VocalNote.CONTENT_URI_NOTES, id);
	 * 
	 *  @param uri The content: URI that identifies the JSON file (only
	 *  {@link #VocalNotes.CONTENT_URI_NOTES}/# and {@ link
	 *  #VocalNotes.CONTENT_URI_JSON} are allowed).
	 *  @param mode A String that indicates the mode of access to
	 *  the file ("r" read-only, "w" write-only).
	 *  @return A ParcelFileDescriptor object for the file.
	 */
   @Override
   public ParcelFileDescriptor openFile(Uri uri, String mode)
         throws IllegalArgumentException, FileNotFoundException {
      // get the code
      int code = URImatcher.match(uri);

      // check the URI and throw an exception if not valid
      if (code != NOTES_ID && code != JSON_ID) {
         throw new IllegalArgumentException("Unknown uri: " + uri);
      }

      // check the access mode and throw an exception if not valid
      if (!"r".equals(mode.toLowerCase()) && !"w".equals(mode.toLowerCase())) {
         throw new FileNotFoundException("Unsupported access mode " + mode
               + ", for uri: " + uri);
      }

      // return the ParcelFileDescriptor by calling convenient openFileHelper()
      // method
      return openFileHelper(uri, mode);
   }
}
